////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNMemory/inc/WNMemory.h"
#include "WNMath/inc/WNRectangle.h"
#include "WNMath/inc/WNBasic.h"
#include "WNMath/inc/WNCircle.h"
#include "WNMath/inc/WNBounds2.h"
#include "WNMath/inc/WNVector2.h"

using namespace WNMath;
using namespace WNMemory;

// Constructors ////////////////////////////////////////////////////////////////////////////////

WNRectangle::WNRectangle() :
    location(0.0f, 0.0f),
    extents(0.0f, 0.0f),
    rotation(0.0f) {
}

WNRectangle::WNRectangle(const WN_FLOAT32* _f) :
    location(_f[0], _f[1]),
    extents(_f[2], _f[3]),
    rotation(_f[4]) {
}

WNRectangle::WNRectangle(const WNPoint2& _location, const WNVector2& _extents, WN_FLOAT32 _rotation) :
    location(_location),
    extents(_extents),
    rotation(_rotation) {
}

WNRectangle::WNRectangle(WN_FLOAT32 _x1, WN_FLOAT32 _y1, WN_FLOAT32 _x2, WN_FLOAT32 _y2, WN_FLOAT32 _rotation) :
    location(_x1, _y1),
    extents(_x2, _y2),
    rotation(_rotation) {
}

// Operators ///////////////////////////////////////////////////////////////////////////////////

WN_BOOL WNRectangle::operator == (const WNRectangle& _rectangle) const {
    return(0 == WNMemCmp(this, &_rectangle, sizeof(WNRectangle)));
}

WN_BOOL WNRectangle::operator != (const WNRectangle& _rectangle) const {
    return(0 != WNMemCmp(this, &_rectangle, sizeof(WNRectangle)));
}

// Members /////////////////////////////////////////////////////////////////////////////////////

WN_VOID WNRectangle::Zero() {
    location.Zero();
    extents.Zero();
    rotation = 0.0f;
}

WN_VOID WNRectangle::Expand(WN_FLOAT32 _amount) {
    extents += _amount;
}

WN_BOOL WNRectangle::Expand(const WNPoint2& _point, WN_BOOL _anchor) {
    const WNPoint2 transformedPoint = ((_point - location).GetRotated(-rotation).ToPoint2());

    if (!_anchor) {
        WNBounds2 bounds = WNBounds2(-extents.ToPoint2(), extents.ToPoint2());

        if (bounds.Expand(transformedPoint)) {
            const WNRectangle newRectange = bounds.ToRectangle();

            location += newRectange.location.ToVector2();
            extents = newRectange.extents;

            return(WN_TRUE);
        }
    } else {
        const WN_FLOAT32 transformedAbsX = WNAbs(transformedPoint.x);
        const WN_FLOAT32 transformedAbsY = WNAbs(transformedPoint.y);
        const WN_FLOAT32 extendsAbsX = WNAbs(extents.x);
        const WN_FLOAT32 extendsAbsY = WNAbs(extents.y);

        WN_BOOL expanded = WN_FALSE;

        if (transformedAbsX > extendsAbsX) {
            extents.x = transformedPoint.x;

            expanded = WN_TRUE;
        }

        if (transformedAbsY > extendsAbsY) {
            extents.y = transformedPoint.y;

            expanded = WN_TRUE;
        }

        return(expanded);
    }

    return(WN_FALSE);
}

WN_BOOL WNRectangle::Expand(const WNBounds2& _bounds, WN_BOOL _anchor) {
    WNPoint2 points[4];

    _bounds.GetPoints(points);

    return(Expand(points[0], _anchor) || Expand(points[1], _anchor) ||
           Expand(points[2], _anchor) || Expand(points[3], _anchor));
}

WN_BOOL WNRectangle::Expand(const WNRectangle& _rectangle, WN_BOOL _anchor) {
    WNPoint2 points[4];

    _rectangle.GetPoints(points);

    return(Expand(points[0], _anchor) || Expand(points[1], _anchor) ||
           Expand(points[2], _anchor) || Expand(points[3], _anchor));
}

WN_BOOL WNRectangle::Expand(const WNCircle& _circle, WN_BOOL _anchor) {
    const WNPoint2 transformedLocation = ((_circle.location - location).GetRotated(-rotation).ToPoint2());
    const WNVector2 componentX = WNVector2(_circle.radius, 0.0f);
    const WNVector2 componentY = WNVector2(0.0f, _circle.radius);
    const WNPoint2 point1 = transformedLocation + componentX;
    const WNPoint2 point2 = transformedLocation - componentX;
    const WNPoint2 point3 = transformedLocation + componentY;
    const WNPoint2 point4 = transformedLocation - componentY;

    return(Expand(point1, _anchor) || Expand(point2, _anchor) ||
           Expand(point3, _anchor) || Expand(point4, _anchor));
}

WN_VOID WNRectangle::Translate(WN_FLOAT32 _x, WN_FLOAT32 _y) {
    location.x += _x;
    location.y += _y;
}

WN_VOID WNRectangle::Translate(const WNVector2& _vector) {
    location += _vector;
}

WN_VOID WNRectangle::Rotate(WN_FLOAT32 _angle) {
    extents.Scale(_angle);
}

WN_VOID WNRectangle::Scale(WN_FLOAT32 _scale) {
    extents.Scale(_scale);
}

WN_VOID WNRectangle::Scale(WN_FLOAT32 _x, WN_FLOAT32 _y) {
    extents.Scale(_x, _y);
}

WN_VOID WNRectangle::Scale(const WNVector2& _scale) {
    extents.Scale(_scale);
}

WN_FLOAT32 WNRectangle::Length() const {
    return(WNAbs(2.0f * extents.x));
}

WN_FLOAT32 WNRectangle::Width() const {
    return(WNAbs(2.0f * extents.y));
}

WN_FLOAT32 WNRectangle::Perimeter() const {
    return(4.0f * extents.x + 4.0f * extents.y);
}

WN_FLOAT32 WNRectangle::Area() const {
    const WNVector2 dimensions = extents * 2.0f;

    return(dimensions.x * dimensions.y);
}

WN_BOOL WNRectangle::IsZero() const {
    return(location.IsZero() && extents.IsZero() && rotation == 0.0f);
}

WN_BOOL WNRectangle::IsInsideOut() const {
    return(extents.x < 0.0f || extents.y < 0.0f);
}

WN_VOID WNRectangle::Set(const WN_FLOAT32* _f) {
    location.x = _f[0];
    location.y = _f[1];
    extents.x = _f[2];
    extents.y = _f[3];
    rotation = _f[4];
}

WN_VOID WNRectangle::Set(const WNPoint2& _location, WNVector2& _extents, WN_FLOAT32 _rotation) {
    location = _location;
    extents = _extents;
    rotation = _rotation;
}

WN_VOID WNRectangle::Set(WN_FLOAT32 _x1, WN_FLOAT32 _y1, WN_FLOAT32 _x2, WN_FLOAT32 _y2, WN_FLOAT32 _rotation) {
    location.x = _x1;
    location.y = _y1;
    extents.x = _x2;
    extents.y = _y2;
    rotation = _rotation;
}

WNRectangle WNRectangle::GetExpanded(WN_FLOAT32 _amount) const {
    WNRectangle rectangle = *this;

    return(rectangle.Expand(_amount), rectangle);
}

WNRectangle WNRectangle::GetExpanded(const WNPoint2& _point, WN_BOOL _anchor) const {
    WNRectangle rectangle = *this;

    return(rectangle.Expand(_point, _anchor), rectangle);
}

WNRectangle WNRectangle::GetExpanded(const WNBounds2& _bounds, WN_BOOL _anchor) const {
    WNRectangle rectangle = *this;

    return(rectangle.Expand(_bounds, _anchor), rectangle);
}

WNRectangle WNRectangle::GetExpanded(const WNRectangle& _rectangle, WN_BOOL _anchor) const {
    WNRectangle rectangle = *this;

    return(rectangle.Expand(_rectangle, _anchor), rectangle);
}

WNRectangle WNRectangle::GetExpanded(const WNCircle& _circle, WN_BOOL _anchor) const {
    WNRectangle rectangle = *this;

    return(rectangle.Expand(_circle, _anchor), rectangle);
}

WNRectangle WNRectangle::GetTranslated(WN_FLOAT32 _x, WN_FLOAT32 _y) const {
    WNRectangle rectangle = *this;

    return(rectangle.Translate(_x, _y), rectangle);
}

WNRectangle WNRectangle::GetTranslated(const WNVector2& _vector) const {
    WNRectangle rectangle = *this;

    return(rectangle.Translate(_vector), rectangle);
}

WNRectangle WNRectangle::GetRotated(WN_FLOAT32 _angle) const {
    WNRectangle rectangle = *this;

    return(rectangle.Rotate(_angle), rectangle);
}

WNRectangle WNRectangle::GetScaled(WN_FLOAT32 _scale) const {
    WNRectangle rectangle = *this;

    return(rectangle.Scale(_scale), rectangle);
}

WNRectangle WNRectangle::GetScaled(WN_FLOAT32 _x, WN_FLOAT32 _y) const {
    WNRectangle rectangle = *this;

    return(rectangle.Scale(_x, _y), rectangle);
}

WNRectangle WNRectangle::GetScaled(const WNVector2& _scale) const {
    WNRectangle rectangle = *this;

    return(rectangle.Scale(_scale), rectangle);
}

WN_VOID WNRectangle::GetPoints(WNPoint2* _points) const {
    const WNVector2 transformedExtents = extents.GetRotated(rotation);
    const WNVector2 transformedFlippedXExtents = (extents * WNVector2(-1.0f, 1.0f)).GetRotated(rotation);
    const WNVector2 transformedFlippedYExtents = (extents * WNVector2(1.0f, -1.0f)).GetRotated(rotation);

    _points[0] = location + transformedExtents;
    _points[1] = location + transformedFlippedXExtents;
    _points[2] = location + transformedFlippedYExtents;
    _points[3] = location - transformedExtents;
}
